//***********************************************************************
// FILE: main.c
// AUTHOR: Yuriy Skriyka
// DATE: 27-03-2012 
// DESCRIPTION: This is an alternative firmware for the LED controller based on ATTiny13A. It uses AMC7135 
//              ICs to provide constant current for the LED. The aim of this firmware is to implement a
//              simple tail light for the bicycle. It has only one blinking mode, which is very energy efficient
//              and exctremly visible on roads. Also it has low voltage detection and check voltage function.
//************************************************************************


#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

#include "Timer.h"
#include "adc.h"



#ifdef USE_PWM
   #define LED_INIT()            DDRB |= _BV(PB1);
   #define LED_ON()              SET_LED_PWM(led_pwm);
   #define LED_OFF()             TURN_PWM_OFF()
   #define LED_LOW()             SET_LED_PWM(led_low_pwm);
   #define LED_GET_STATE()       IS_LED_ON()
#else
   #define LED_INIT()            DDRB |= _BV(PB1); PORTB &= ~_BV(PB1);
   #define LED_ON()              PORTB |= _BV(PB1);
   #define LED_OFF()             PORTB &= ~_BV(PB1);
   #define LED_LOW()             LED_OFF()
   #define LED_GET_STATE()       PORTB & _BV(PB1)
#endif // USE_PWM



#define KEY_INIT()      DDRB &= ~_BV(PB4); PORTB |= _BV(PB4);     // connect pull-up resistor
#define KEY_CHECK()     ( !(PINB & _BV(PB4)) )     // key is inverted

// ----------------------------------------------------------------------------

// contains current time from the timer
static uint32_t sys_time;
static uint32_t timer_last_LED_blink;
static uint32_t timer_last_Key_check;
static uint32_t timer_last_check_voltage;


// ----------------------------------------------------------------------------

#define MODE_OFF              0
#define MODE_GOTO_OFF         1
#define MODE_BLINK_1          2
#define MODE_BLINK_2          3
#define MODE_LOW_VOLTAGE      10


static uint8_t mode;// = MODE_GOTO_OFF;   // goto sleep at power on
static uint8_t led_pwm;// = 0xFF;         // full power
static uint8_t led_low_pwm;// = 10;       // do not turn the led off completely, just dimm it 
// ----------------------------------------------------------------------------

#define LED_BLINK_DELAY             10       // led process interval 10ms
#define KEY_CHECK_DELAY             100      // key process interval 100ms
#define VOLTAGE_CHECK_DELAY         1000     // check voltage interval 1000ms

// ----------------------------------------------------------------------------

EMPTY_INTERRUPT(PCINT0_vect);       // just empty interrupt

// ----------------------------------------------------------------------------

void onLedBlinkProcess(void)
{
   static uint8_t led_on = 0;
   static uint8_t count = 0;
   uint8_t led_on_duration;
   uint8_t led_off_duration;

   if (mode == MODE_BLINK_1)
   {
      led_on_duration = 3;
      led_off_duration = 30;
   }
   else if (mode == MODE_BLINK_2)
   {
      led_on_duration = 1;
      led_off_duration = 70;
   }
   else if (mode == MODE_LOW_VOLTAGE)
   {
      led_on_duration = 40;
      led_off_duration = 40;
   }
   else 
   {
      return;
   }

   count++;

   if (led_on)
   {
      if (count > led_on_duration)
      {
         led_on = 0;    // turn led off
         LED_LOW();
         count = 0;
      }
   }
   else
   {
      if (count > led_off_duration)
      {
         led_on = 1;
         LED_ON();
         count = 0;
      }
   }
}

// ----------------------------------------------------------------------------
#define DIODE_05V       // an alternative diode with 0.5V voltage drop instead of stock 0.3V

#ifdef DIODE_05V
   #define ADC_VOLTAGE_FULL       185   //  ADC value 185 -> 4.2V
   #define ADC_VOLTAGE_LOW        144   //  ADC value 144 -> 3.2V
   #define ADC_VOLTAGE_EMPTY      123   //  ADC value 123 -> 2.8V
#else
   #define ADC_VOLTAGE_FULL       206   //  ADC value 206 -> 4.2V
   #define ADC_VOLTAGE_LOW        153   //  ADC value 153 -> 3.2V
   #define ADC_VOLTAGE_EMPTY      132   //  ADC value 132 -> 2.8V
#endif

// ----------------------------------------------------------------------------

void showVoltageLevel(void)
{
   uint8_t voltage = GET_VOLTAGE();
   uint8_t numberOfBlinks = 1;

   if (voltage > ADC_VOLTAGE_EMPTY)
   {
      numberOfBlinks = (voltage - ADC_VOLTAGE_EMPTY) / ( (ADC_VOLTAGE_FULL - ADC_VOLTAGE_EMPTY) / 4) + 1;    // 4 + 1 number of blinks on full charge
   }
   
   led_pwm = 10;        // don't need to go on full power just to show the voltage

   while (numberOfBlinks--)
   {
      LED_ON();
      timerSleep(500);
      LED_OFF();
      timerSleep(500);
   }
}

// ----------------------------------------------------------------------------

#define KEY_LONG_PRESS     5

// ----------------------------------------------------------------------------

void onKeyProcess(void)
{
   static uint8_t oldState = 0;
   static uint8_t duration = 0;

   uint8_t newState = KEY_CHECK();

   if (newState == oldState)
   {     // the key stat hasn't changed

      if (duration < 0xFF) duration++;

      if (newState && duration == KEY_LONG_PRESS)
      {     // long press detected
         if (mode == MODE_OFF)
         {
            showVoltageLevel();
         }

         mode = MODE_GOTO_OFF;
         duration = 0;        // set to zero because when we wake up the key will be pressed
      }
   }
   else
   {  // the key state has changed

      if (!newState)
      {     // the key has just been released
         if (mode == MODE_OFF)
         {
            mode = MODE_BLINK_1;
         }
         else if (mode == MODE_BLINK_1)
         {
            mode = MODE_BLINK_2;
         }
         else
         {
            mode = MODE_BLINK_1;
         }
         
      }

      duration = 0; // reset duration
   }

   oldState = newState;
}

// ----------------------------------------------------------------------------

void onVoltageCheckProcess(void)
{
   uint8_t currVoltage;
   
   if (mode == MODE_OFF || LED_GET_STATE()) return;  // don't check if the led is ON because of the voltage drop

   currVoltage = GET_VOLTAGE();

   if (currVoltage < ADC_VOLTAGE_EMPTY)
   {
      mode = MODE_GOTO_OFF;
   }
   else if (currVoltage < ADC_VOLTAGE_LOW)
   {
      mode = MODE_LOW_VOLTAGE;
      led_pwm = 10;     // reduce led power
      led_low_pwm = 0;
   }
}

// ----------------------------------------------------------------------------

int main(void)
{
   LED_INIT();
   KEY_INIT();

   initTimer();
   initADC();

   GIMSK |= _BV(PCIE);     // enable interrupt from pin change
   PCMSK |= _BV(PCINT4);      // enable interrupt from PCINT4

   set_sleep_mode(SLEEP_MODE_PWR_DOWN);

   sei();  // enable global interrupt

   sys_time = getTimer();
   timer_last_LED_blink = sys_time;
   timer_last_Key_check = sys_time;
   timer_last_check_voltage = sys_time;

   mode = MODE_GOTO_OFF;

   while (1)
   {
      if (mode == MODE_GOTO_OFF)
      {
         mode = MODE_OFF;
         led_pwm = 0xFF;
         led_low_pwm = 2;
         LED_OFF();
         ADC_OFF();     // turn ADC off to reduce power consumption
         
         while (KEY_CHECK());   // wait for the key to get released
         
         timerSleep(100);  // skip key jitter
         sleep_mode();  
         ADC_ON();
         timerSleep(100);  // skip key jitter because the only way to wake up is by pressing the key
      }

      sys_time = getTimer();

	   if (timer_last_LED_blink + LED_BLINK_DELAY <= sys_time)
      {
         timer_last_LED_blink = sys_time;

         onLedBlinkProcess();

         continue;
      }

      if (timer_last_Key_check + KEY_CHECK_DELAY <= sys_time)
      {
         timer_last_Key_check = sys_time;
         
         onKeyProcess();

         continue;
      }

      if (timer_last_check_voltage + VOLTAGE_CHECK_DELAY <= sys_time)
      {
         timer_last_check_voltage = sys_time;
         
         onVoltageCheckProcess();

         continue;
      }
   }
  
   return 0;
}